This notebook reports on several approaches to selecting tree stems
for individual-scale species classification, uniting Kueppers, Worsham
et al. forest inventory data with Falco species classification map
derived from the 2018 NEON spectroscopy mission. The approaches range
from least to most conservative, that is, from preserving all trees to
removing all but those in the uppermost canopy. Maps, agreement
statistics, and agreement figures are shown for each approach.
Pipeline
- Align geolocated tree stem objects to classification raster.
- Define a buffer of radius \(r\)
around each tree stem object, approximating crown area.
- Filter crown objects according to one of 4 specified
procedures.
- Plot a map of crown objects and classification data at an example
site.
- For each crown object, extract intersecting raster values by
majority vote.
- Compute accuracy statistics.
- Generate agreement figures.
1. Least conservative approach: keep every tree
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
849 |
244 |
1615 |
2 |
0 |
| PICO |
10 |
535 |
23 |
0 |
0 |
| PIEN |
254 |
85 |
933 |
0 |
0 |
| POTR |
4 |
5 |
16 |
2 |
0 |
| UNKN |
1 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.27 |
0.51 |
0.49 |
0.52 |
Probability density function


2. Less conservative
Keep any tree whose height is in the 90th percentile or higher. For
all other trees, only keep them if (a) they don’t intersect another
tree’s crown area or (b) if the tree whose area they intersect is not in
the 90th percentile height.
- Apply a height-dependent buffer of radius \(r=0.03H + 0.5\), around every tree.
- For each tree \(t_i\):
- if: \(H \ge H_{90pctl}\), keep
\(t_i\).
- else:
- Test whether crown area sits entirely within another tree crown
area.
- if True: discard \(t_i\)
- if False:
- Test whether crown area of \(t_i\)
intersects any other tree crown area.
- if True: test whether any of the trees \(t_i\) intersects has \(H \ge H_{90pctl}\)
- if True: discard \(t_i\)
- if False: keep \(t_i\)
- if False: keep \(t_i\)
# Create canopy filter
canopy.filter.a <- unlist(lapply(1:nrow(stem.buff), \(i) {
if(stem.buff[i,]$Height < quantile(stem.buff$Height, .9)) {
if(length(stem.within[[i]])<=1) {
tst <- stem.buff[i,]$Height > 0.9 * stem.buff[stem.overlap[[i]],]$Height &
!any(stem.buff[stem.overlap[[i]],]$Height >= quantile(stem.buff$Height, .8))
tst <- prod(tst)
} else {
tst <- 0 }
} else {
tst <- 1
}
as.logical(tst)
}
))
# Apply canopy filter to buffered stems
stem.filt.a <- stem.buff[canopy.filter.a,]
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
158 |
45 |
282 |
0 |
0 |
| PICO |
2 |
117 |
7 |
0 |
0 |
| PIEN |
59 |
26 |
341 |
0 |
0 |
| POTR |
3 |
2 |
11 |
1 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.35 |
0.59 |
0.55 |
0.62 |
Probability density function


3. More conservative
Keep any tree whose height is in the 90th percentile or higher. For
all other trees, only keep them if (a) they don’t intersect another
tree’s crown area.
- Apply a height-dependent buffer of radius \(r=0.042H + 0.675\), around every tree.
- For each tree \(t_i\):
- if: \(H \ge H_{90pctl}\), keep
\(t_i\).
- else:
- Test whether crown area of \(t_i\)
intersects any other tree crown area.
- if True: discard \(t_i\)
- if False: keep \(t_i\)
# Create canopy filter
canopy.filter.b <- unlist(lapply(1:nrow(stem.buff), \(i) {
if(stem.buff[i,]$Height < quantile(stem.buff$Height, .90)) {
if(length(stem.within[[i]])<=1) {
if(length(stem.overlap[[i]])) {
tst <- 0
} else {
tst <- 1
}
} else {
tst <- 0
}
} else {
tst <- 1
}
as.logical(tst)
}
))
# Apply canopy filter
stem.filt.b <- stem.buff[canopy.filter.b,]
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
97 |
11 |
191 |
0 |
0 |
| PICO |
0 |
12 |
3 |
0 |
0 |
| PIEN |
45 |
15 |
282 |
0 |
0 |
| POTR |
0 |
1 |
1 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.23 |
0.59 |
0.56 |
0.63 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.68 |
0.61 |
0.32 |
0.87 |
0.32 |
0.68 |
0.44 |
0.22 |
0.15 |
0.45 |
0.65 |
| Class: PICO |
0.31 |
1.00 |
0.80 |
0.96 |
0.80 |
0.31 |
0.44 |
0.06 |
0.02 |
0.02 |
0.65 |
| Class: PIEN |
0.59 |
0.67 |
0.82 |
0.38 |
0.82 |
0.59 |
0.69 |
0.72 |
0.43 |
0.52 |
0.63 |
| Class: POTR |
NA |
1.00 |
NA |
NA |
0.00 |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Probability density function


Most conservative
Keep only trees in the 90th percentile of height or higher.
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
65 |
4 |
122 |
0 |
0 |
| PICO |
0 |
2 |
3 |
0 |
0 |
| PIEN |
39 |
10 |
246 |
0 |
0 |
| POTR |
0 |
0 |
0 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.22 |
0.64 |
0.59 |
0.68 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.62 |
0.67 |
0.34 |
0.87 |
0.34 |
0.62 |
0.44 |
0.21 |
0.13 |
0.39 |
0.65 |
| Class: PICO |
0.12 |
0.99 |
0.40 |
0.97 |
0.40 |
0.12 |
0.19 |
0.03 |
0.00 |
0.01 |
0.56 |
| Class: PIEN |
0.66 |
0.59 |
0.83 |
0.36 |
0.83 |
0.66 |
0.74 |
0.76 |
0.50 |
0.60 |
0.63 |
| Class: POTR |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Probability density function


Most conservative, with a static 3 px window
Keep only trees in the 90th percentile of height or higher and search
only 3 px around
Map

Confusion matrix
## ABLA PICO PIEN POTR UNKN NA's
## 116 22 329 0 0 33
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
67 |
10 |
101 |
0 |
0 |
| PICO |
0 |
2 |
3 |
0 |
0 |
| PIEN |
49 |
10 |
225 |
0 |
0 |
| POTR |
0 |
0 |
0 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.22 |
0.63 |
0.58 |
0.67 |
Probability density function


Compare accuracy
| Run |
Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| Least Conserv |
0.27 |
0.51 |
0.49 |
0.52 |
| Less Conserv |
0.35 |
0.59 |
0.55 |
0.62 |
| More Conserv |
0.23 |
0.59 |
0.56 |
0.63 |
| Most Conserv - Moving window |
0.22 |
0.64 |
0.59 |
0.68 |
| Most Conserv - 3px window |
0.22 |
0.63 |
0.58 |
0.67 |